package vn.edu.uit.hotelbooking.utils.Image;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import vn.edu.uit.hotelbooking.R;
import vn.edu.uit.hotelbooking.utils.ImageUtils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;

public class ImageLoader {
	
	public static final String LOG_TAG = "ImageLoader";
	
	private static final int NUM_THREADS = 5;
	private static final int RES_EMPTY_IMAGE = R.drawable.img_empty;
	
	private MemoryCache memoryCache = new MemoryCache();
	FileCache fileCache;
	
	private Map<ImageView, String> imageViews = Collections
			.synchronizedMap(new WeakHashMap<ImageView, String>());
	
	private ExecutorService executor;
	// Handler to post task for updating UI
	private Handler handler = new Handler();
	
	public ImageLoader(Context context) {
		this.fileCache = new FileCache(context);
		this.executor = Executors.newFixedThreadPool(NUM_THREADS);
	}
	
	public void displayImage(String url, ImageView imageView) {
	    if(url.equalsIgnoreCase("")){
	        imageView.setImageResource(RES_EMPTY_IMAGE);
	    }
	    else{
	        imageViews.put(imageView, url);
	        Bitmap bitmap = memoryCache.get(url);
	        if(bitmap != null) {
	            imageView.setImageBitmap(bitmap);
	        } else {
	            queueImage(url, imageView);
	        }
	    }
	}
	
	private void queueImage(String url, ImageView imageView) {
		ImageToLoad imageToLoad = new ImageToLoad(url, imageView);
		executor.submit(new ImageLoaderTask(imageToLoad));
	}
	
	private Bitmap getBitmap(String url) {
		File f = fileCache.getFile(url);

		// from SD cache
		Bitmap b = decodeFile(f);
		if (b != null)
			return b;

		// from web
		try {
			Bitmap bitmap = null;
			URL imageUrl = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) imageUrl
					.openConnection();
			conn.setConnectTimeout(30000);
			conn.setReadTimeout(30000);
			conn.setInstanceFollowRedirects(true);
			InputStream is = conn.getInputStream();
			OutputStream os = new FileOutputStream(f);
			ImageUtils.copyStream(is, os);
			os.close();
			conn.disconnect();
			bitmap = decodeFile(f);
			return bitmap;
		} catch (Throwable ex) {
			ex.printStackTrace();
			if (ex instanceof OutOfMemoryError)
				memoryCache.clear();
			return null;
		}
	}
	
	// decodes image and scales it to reduce memory consumption
	private Bitmap decodeFile(File f) {
		try {
			// decode image size
			BitmapFactory.Options o = new BitmapFactory.Options();
			o.inJustDecodeBounds = true;
			FileInputStream stream1 = new FileInputStream(f);
			BitmapFactory.decodeStream(stream1, null, o);
			stream1.close();

			// Find the correct scale value. It should be the power of 2.
			final int REQUIRED_SIZE = 70;
			int width_tmp = o.outWidth, height_tmp = o.outHeight;
			int scale = 1;
			while (true) {
				if (width_tmp / 2 < REQUIRED_SIZE
						|| height_tmp / 2 < REQUIRED_SIZE)
					break;
				width_tmp /= 2;
				height_tmp /= 2;
				scale *= 2;
			}

			// decode with inSampleSize
			BitmapFactory.Options o2 = new BitmapFactory.Options();
			o2.inSampleSize = scale;
			FileInputStream stream2 = new FileInputStream(f);
			Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);
			stream2.close();
			return bitmap;
		} catch (FileNotFoundException e) {
			Log.e(LOG_TAG, e.getMessage());
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private boolean imageViewReused(ImageToLoad imageToLoad) {
		String tag = imageViews.get(imageToLoad.imageView);
		if (tag == null || !tag.equals(imageToLoad.url))
			return true;
		return false;
	}
	
	public void clearCache() {
		memoryCache.clear();
		fileCache.clear();
	}
	
	/**
	 * Run in background for load bitmap from server
	 */
	class ImageLoaderTask implements Runnable {
		ImageToLoad imageToLoad;
		
		public ImageLoaderTask(ImageToLoad imageToLoad) {
			this.imageToLoad = imageToLoad;
		}
		
		@Override
		public void run() {
			try {
				if (imageViewReused(imageToLoad))
					return;
				Bitmap bitmap = getBitmap(imageToLoad.url);
				memoryCache.put(imageToLoad.url, bitmap);
				if (imageViewReused(imageToLoad))
					return;
				
				ImageDisplayer imageDisplayer = new ImageDisplayer(bitmap, imageToLoad);
				handler.post(imageDisplayer);
			} catch (Exception e) {
				Log.e(LOG_TAG, e.getMessage());
			}
		}
	}
	
	/**
	 * This task will be posted to UI thread for updating ImageView
	 */
	class ImageDisplayer implements Runnable {
		Bitmap bitmap;
		ImageToLoad imageToLoad;
		
		public ImageDisplayer(Bitmap bitmap, ImageToLoad imageToLoad) {
			this.bitmap = bitmap;
			this.imageToLoad = imageToLoad;
		}
		
		@Override
		public void run() {
			if (imageViewReused(imageToLoad))
				return;
			if(bitmap != null) {
				imageToLoad.imageView.setImageBitmap(bitmap);
			} else {
				imageToLoad.imageView.setImageResource(RES_EMPTY_IMAGE);
			}
		}
	}
	
	class ImageToLoad {
		
		public String url;
		public ImageView imageView;
		
		public ImageToLoad(String url, ImageView imageView) {
			this.url = url;
			this.imageView = imageView;
		}
	}
}
